diff -rup0 a/comment_subject.info b/comment_subject.info
--- comment_subject.info	Tue Sep 15 12:17:10 2009
+++ comment_subject.info	Tue Sep 15 12:17:36 2009
@@ -1,0 +1,1 @@
+; $Id$
@@ -2,6 +3,4 @@ name = Comment subjects
-description = Provides a default comment subject: 'Re: [parent comment/node title]'.
-; Information added by drupal.org packaging script on 2008-11-06
-version = "6.x-1.4"
-core = "6.x"
-project = "comment_subject"
-datestamp = "1225962012"
+description = Provides a default comment subject like: "Re: [parent comment/node title]", auto-incremental numbering, and optional integration with token for author, date, etc.
+version = 6.x-2.5
+core = 6.x
+project = comment_subject
@@ -8,0 +8 @@ datestamp = "1225962012"
+dependencies[] = comment
diff -rup0 a/comment_subject.module b/comment_subject.module
--- comment_subject.module	Tue Sep 15 12:17:10 2009
+++ comment_subject.module	Tue Sep 15 12:17:36 2009
@@ -2 +2,67 @@
-// $Id: comment_subject.module,v 1.7.2.1 2008/11/06 08:53:10 ahoeben Exp $
+// $Id$
+
+define('COMMENT_SUBJECT__DEFAULT_SUBJECT_PREFIX', 'Re: ');
+define('COMMENT_SUBJECT__DEFAULT_SUBJECT_BETWEEN', '[comment-parent-title]');
+define('COMMENT_SUBJECT__DEFAULT_SUBJECT_SUFFIX', ''); // can't set up "by [comment-author-name]" unless token module being a required dependency
+
+/**
+ * Implementation of hook_enable().
+ */
+function comment_subject_enable() {
+  // weight above token module's (10) to avoid redeclaring theme_token_list
+  db_query("UPDATE {system} SET weight = 20 WHERE name = 'comment_subject' AND type = 'module'");
+}
+
+/**
+ * Implementation of hook_init().
+ */
+function comment_subject_init() {
+  // replacement tokens provided by this module will be available even without token module
+  // i.e. [comment-parent-title], [comment-auto-numbering]
+  //if (module_exists('token') && !function_exists('comment_subject_token_values')) {
+  module_load_include('inc', 'comment_subject', 'comment_subject.token');
+}
+
+/**
+ * Implementation of hook_theme().
+ */
+function comment_subject_theme(&$cache, $type, $module_name, $module_path) {
+  $theme = array();
+  // conditional declare theme_token_list, 
+  // since it might be already provided by token module (if it is enabled)
+  if (!isset($cache['token_list'])) {
+    $theme['token_list'] = array(
+      'arguments' => array('token_list' => array(), 'prefix' => '[', 'suffix' => ']')
+    );
+  }
+  return $theme;
+}
+
+/**
+ * Implementation of hook_help().
+ */
+function comment_subject_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#comment_subject':
+      $output = '<p>' . t('Provides a default comment subject like: "Re: [parent comment/node title]", auto-incremental numbering, and optional integration with token for author, date, etc. This option can be set on a per content type basis.') . '</p>';
+      $output .= '<p>' . t('Users can still edit the comment subject to their liking if comment titles are enabled in the comment settings.') . '</p>';
+      $output .= '<p>' . t('When using [comment-parent-title] comment\'s subject is derived from the node title, or the comment that the new comment is a reply to. In addition using non-repetitive prefix/suffix allows achieving (for instance) avoiding repeated "Re:" prefix and/or "by username" suffix.') . '</p>';
+      $output .= '<p>' . t('When using [comment-auto-numbering] option comment\'s subject are auto-incremental (e.g. "#1", "#2", an so on).')
+        . '<br/>' . t('NOTE: to achieve [comment-auto-numbering] replacement works as expected previous existing comments shouldn\'t be deleted. Otherwise numbering sequence won\'t be guaranted (i.e. existing comments won\'t get updated and new comments will count just existing ones).') . '</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implementation of hook_node_type().
+ */
+function comment_subject_node_type($op, $info) {
+  switch ($op) {
+    case 'delete':
+      $node_type = $info->type;
+      variable_del('comment_subject_field:default_value_prefix_' . $node_type);
+      variable_del('comment_subject_field:default_value_between_' . $node_type);
+      variable_del('comment_subject_field:default_value_suffix_' . $node_type);
+      break;
+  }
+}
@@ -8,20 +74,54 @@ function comment_subject_form_alter(&$fo
-  if ($form_id == 'comment_form' && $form['subject']['#default_value']=='') {
-    switch (arg(0)) {
-      case 'node':
-        $node = node_load(array('nid' => arg(1)));
-        $subject = $node->title;
-        break;
-      case 'comment':
-        if (arg(1)=='reply') {
-          if (is_numeric(arg(3))) {
-            $comment = _comment_load(arg(3));
-            $subject = $comment->subject;
-          } else {
-            $node = node_load(arg(2));
-            $subject = $node->title;
-          }
-        } else if (arg(1)=='edit' && is_numeric(arg(2))) {
-          $comment = _comment_load(arg(2));
-          $subject = $comment->subject;
-        }
-        break;
+  // keep this condition consistent with comment.module
+  if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
+    $form['comment']['comment_subject_field']['#weight'] = 10;
+    $node_type = $form['#node_type']->type;
+    
+    $prefix_suffix_msg = t('NOTE: only [comment-*] replacements are supported, global replacements won\'t let the stripping algorith to behave as expected. Nevertheless, other immutable tokens might be used (for instance [site-name], [site-mail]), but if their values change in time the prefix/suffix stripping shall fail.');  
+    $form['comment']['comment_subject_field:default_value_prefix'] = array(
+      '#title' => 'Non repetitive prefix',
+      '#description' => t('Non repetitive prefix for default subject which will be stripped from [comment-parent-title].')
+        . '<br/>' . t('e.g. "Re: ", "#[comment-auto-numbering]", "#[comment-auto-numbering] Re: "')
+        . '<br/>' . $prefix_suffix_msg,
+      '#type' => 'textfield',
+      '#default_value' => variable_get('comment_subject_field:default_value_prefix_' . $node_type, COMMENT_SUBJECT__DEFAULT_SUBJECT_PREFIX), 
+      '#weight' => 11,
+    );
+    $form['comment']['comment_subject_field:default_value_between'] = array(
+      '#title' => 'Default subject',
+      '#description' => t('Default value for subject field (e.g. "[comment-parent-title]").')
+        . '<br/>' .  t(' (Note that whenever a user leave subject field blank it is filled with firsts comment\'s words according to core behavior)'),
+      '#type' => 'textfield',
+      '#default_value' => variable_get('comment_subject_field:default_value_between_' . $node_type, COMMENT_SUBJECT__DEFAULT_SUBJECT_BETWEEN),
+      '#weight' => 12,
+    );
+    $form['comment']['comment_subject_field:default_value_suffix'] = array(
+      '#title' => 'Non repetitive suffix',
+      '#description' => t('Non repetitive suffix for default subject which will be stripped from [comment-parent-title]')
+        . '<br/>' . t('e.g. "by [comment-author-name]", "on [comment-mm]-[comment-d]-[comment-yyyy]", "by [comment-author-name] on [comment-mm]-[comment-d]-[comment-yyyy]"')
+        . '<br/>' . $prefix_suffix_msg,
+      '#type' => 'textfield',
+      '#default_value' => variable_get('comment_subject_field:default_value_suffix_' . $node_type, COMMENT_SUBJECT__DEFAULT_SUBJECT_SUFFIX), 
+      '#weight' => 13,
+    );
+    
+    $form['comment']['token_replacements'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Replacements for default subject'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      '#weight' => 13,
+    );
+    
+    $requires_comment_subject_field_disabled = '. ' . t('AFTER submit');
+    if (module_exists('token')) {
+      $token_list = token_get_list('comment');
+      $form['comment']['token_replacements']['#description'] = t('Note that not every available pattern makes sense for comment\'s default subject.'); 
+    }
+    else {
+      $token_list = comment_subject_token_list('comment');
+      $form['comment']['token_replacements']['#description'] = t('If !link module were enabled more replacements would be available.', array('!link' => '<a href="drupal.org/project/token">token</a>')); 
+    }
+    foreach($token_list['comment'] as $key => $value) {
+      if (comment_subject_requires_comment_subject_field_disabled($key)) {
+        $token_list['comment'][$key] .= $requires_comment_subject_field_disabled;
+      }
@@ -29,2 +129,45 @@ function comment_subject_form_alter(&$fo
-    if (!preg_match('/^' . preg_quote(t('Re:')) . '/i', $subject)) {
-      $subject = t('Re:') . ' ' . $subject;
+    $form['#validate'][] = 'comment_subject_validate_replacement';
+    $form['comment']['token_replacements']['#description'] .= '<br/>' . t('Replacements marked with "AFTER submit" require <em>comment subject field</em> to be disabled.'); 
+    
+    $form['comment']['token_replacements']['table'] = array(
+      '#value' => theme('token_list', $token_list),
+    );
+  }
+  elseif($form_id == 'comment_form' && empty($form['subject']['#default_value'])) {
+    // nevermind whether #type should or shouldn't be hidden
+    // just update the #default_value
+    // which is compatible with comment_update_6002 and preserves backward compatibility
+    if (!isset($form['subject'])) $form['subject']['#type'] = 'hidden';
+    $comment = (object)array('nid' => $form['nid']['#value'], 'pid' => $form['pid']['#value'], 'cid' => $form['cid']['#value']);
+    $form['subject']['#default_value'] = _comment_subject_get_subject($comment);
+  }
+}
+
+function comment_subject_requires_comment_subject_field_disabled($key) {
+  return !in_array($key, _comment_subject_allowed_replacements_comment_subject_field_enabled());
+}
+
+function _comment_subject_allowed_replacements_comment_subject_field_enabled() {
+  return array('comment-nid', 'comment-pid', 'comment-parent-title', 'comment-parent-title-raw');
+}
+
+function _comment_subject_get_node_type($nid) {
+  // couldn't figure out a way to get the related node type without loading the node
+  // whole comment.module works loading the node
+  $node = node_load($nid);
+  $node_type = node_get_types('type', $node)->type;
+  return $node_type;
+}
+
+function _comment_subject_token_replace($str, $comment) {
+  if (module_exists('token')) {
+    $result = token_replace($str, 'comment', $comment);
+  }
+  else {
+    // Note: duplicating code should be avoided,
+    // but this is the (little) cost to be independent from token module
+    // and not having it as a required dependency
+    $tokens = comment_subject_token_values('comment', $comment);
+    foreach ($tokens as $key => $value) {
+      $tokens[$key] = '[' . $key . ']';
+      $values[] = $value;
@@ -32,2 +175,7 @@ function comment_subject_form_alter(&$fo
-    // comment subjects can not be longer than 64 characters
-    $subject = truncate_utf8($subject, 64, TRUE, TRUE);
+    $result = str_replace($tokens, $values, $str);
+  }
+  return $result;
+}
+
+function _comment_subject_get_subject($comment) {
+  $node_type = _comment_subject_get_node_type($comment->nid);
@@ -35,5 +183,12 @@ function comment_subject_form_alter(&$fo
-    if (variable_get('comment_subject_field', 1) == 1) {
-      $form['subject']['#default_value'] = $subject;
-    } else {
-      // comment subject disabled in comment settings
-      $form['subject'] = array('#type' => 'hidden', '#default_value' => $subject);
+  $prefix = variable_get('comment_subject_field:default_value_prefix_' . $node_type, COMMENT_SUBJECT__DEFAULT_SUBJECT_PREFIX);
+  $between = variable_get('comment_subject_field:default_value_between_' . $node_type, COMMENT_SUBJECT__DEFAULT_SUBJECT_BETWEEN);
+  $suffix = variable_get('comment_subject_field:default_value_suffix_' . $node_type, COMMENT_SUBJECT__DEFAULT_SUBJECT_SUFFIX);
+  
+  if ($comment->pid && (strpos($between, '[comment-parent-title]') !== FALSE)) {
+    $parent_comment = _comment_load($comment->pid);
+    $parent_prefix = _comment_subject_token_replace($prefix, $parent_comment);
+    $parent_suffix = _comment_subject_token_replace($suffix, $parent_comment);
+    
+    $parent_title = _comment_subject_token_replace('[comment-parent-title]', $comment);
+    if (!empty($parent_prefix) && strpos($parent_title, $parent_prefix) === 0) {
+      $parent_title = substr($parent_title, strlen($parent_prefix));
@@ -40,0 +196,29 @@ function comment_subject_form_alter(&$fo
+    // strrpos only works for a single character in PHP4
+    if (!empty($parent_suffix) && strpos(strrev($parent_title), strrev($parent_suffix)) === 0) {
+      $parent_title = strrev(substr(strrev($parent_title), strlen($parent_suffix)));
+    }
+    // $parent_title was already stripped of $parent_prefix and $parent_suffix
+    $between = str_replace('[comment-parent-title]', $parent_title, $between);
+  }
+  
+  $subject = _comment_subject_token_replace($prefix . $between . $suffix, $comment);
+  
+  // comment subjects can not be longer than 64 characters
+  $subject = truncate_utf8($subject, 64, TRUE, TRUE);
+  
+  return $subject;
+}
+
+function comment_subject_comment($edit, $op) {
+  switch($op) {
+    case 'insert':
+      $node_type = _comment_subject_get_node_type($edit['nid']);
+      // if user subject is enabled can't figure out whether should be updated or leave user input as is
+      if (!variable_get('comment_subject_field_' . $node_type, 1)) {
+        // update required for every [comment-*] replacement not listed in _comment_subject_allowed_replacements_comment_subject_field_enabled()
+        $comment = _comment_load($edit['cid']);
+        $subject = _comment_subject_get_subject($comment);
+        $query = "UPDATE {comments} SET subject = '%s' WHERE cid = %d";
+        db_query($query, $subject, $edit['cid']);
+      }
+      break;
@@ -44 +228,44 @@ function comment_subject_form_alter(&$fo
-?>
\ No newline at end of file
+function comment_subject_validate_replacement($form, &$form_state) {
+  $element_keys = array('comment_subject_field:default_value_prefix', 'comment_subject_field:default_value_between', 'comment_subject_field:default_value_suffix');
+  
+  $matches = array();
+  if ($form_state['values']['comment_subject_field']) {
+    foreach($element_keys as $element_key) {
+      if (preg_match_all('/\\[(comment-.*)\\]/U', $form_state['values'][$element_key], $matches)) {
+        $unsupported = array();
+        foreach($matches[1] as $replacement) {
+          if (comment_subject_requires_comment_subject_field_disabled($replacement)) {
+            $unsupported[] = $replacement;
+          } 
+        }
+        if (!empty($unsupported)) {
+          form_set_error($element_key, t('There are some token replacements in use which requires <em>comment subject field</em> to be disabled: @unsupported', array('@unsupported' => '[' . implode('], [', $unsupported) . ']')));
+        }
+      }
+    }
+  }
+}
+
+// conditional declare theme_token_list, 
+// since it might be already provided by token module (if it is enabled)
+// Note: duplicating code should be avoided,
+// but this is the (little) cost to be independent from token module
+// and not having it as a required dependency
+if (!function_exists('theme_token_list')) {
+  function theme_token_list($token_list, $prefix = '[', $suffix = ']') {
+    $headers = array(t('Token'), t('Replacement value'));
+    $rows = array();
+    foreach ($token_list as $key => $category) {
+      $rows[] = array(array('data' => drupal_ucfirst($key) .' '. t('tokens'), 'class' => 'region', 'colspan' => 2));
+      foreach ($category as $token => $description) {
+        $row = array();
+        $row[] = $prefix . $token . $suffix;
+        $row[] = $description;
+        $rows[] = $row;
+      }
+    }
+  
+    $output = theme('table', $headers, $rows, array('class' => 'description'));
+    return $output;
+  }
+}
diff -Nrup0 /dev/null b/comment_subject.token.inc
--- /dev/null
+++ comment_subject.token.inc	Tue Sep 15 12:17:36 2009
@@ -0,0 +1,54 @@
+<?php
+// $Id$
+
+function comment_subject_token_list($type = 'all') {
+  if ($type == 'comment' || $type == 'all') {
+    $tokens = array();
+
+    $tokens['comment']['comment-pid']              = t('Comment\'s parent ID (just for parent being another comment, otherwise zero)');
+    $tokens['comment']['comment-parent-title']     = t('Paren\'s title (the parent might be the node or a another comment)');
+    $tokens['comment']['comment-parent-title-raw'] = t('Paren\'s title. WARNING - raw user input');
+    $tokens['comment']['comment-auto-numbering']   = t('Auto-numbering with respect to node\'s comments count');
+    
+    return $tokens;
+  }
+}
+
+function comment_subject_token_values($type, $object = NULL) {
+  static $comments_count_cache = array();
+  
+  if ($type == 'comment') {
+    $comment = (object)$object;
+    // @TODO: support uninitialized pid? (forms might have it uninitialized)
+    if (!isset($comment->pid)) $comment->pid = 0;
+    
+    // [comment-parent-title]
+    if ($comment->pid) { 
+      $parent = _comment_load($comment->pid);
+      $parent_title = $parent->subject;
+    }
+    else {
+      $parent = node_load($comment->nid);
+      $parent_title = $parent->title;
+    }
+    
+    // [comment-auto-numbering]
+    if (isset($comment->cid)) {
+      if (!isset($comments_count_cache[$comment->cid])) {
+        $query = 'SELECT COUNT(cid) FROM {comments} WHERE nid = %d AND cid < %d ORDER BY cid ASC';
+        $comments_count_cache[$comment->cid] = db_result(db_query($query, $comment->nid, $comment->cid));
+      }
+      $comments_count = $comments_count_cache[$comment->cid];
+    }
+    else {
+      $comments_count = comment_num_all($comment->nid);
+    }
+    
+    $tokens['comment-pid']              = $comment->pid;
+    $tokens['comment-parent-title']     = check_plain($parent_title);
+    $tokens['comment-parent-title-raw'] = $parent_title;
+    $tokens['comment-auto-numbering']   = ($comments_count + 1);
+    
+    return $tokens;
+  }
+}
